package cn.juque.common.filters;

import cn.hutool.extra.spring.SpringUtil;
import cn.juque.common.base.BaseResponseDTO;
import cn.juque.common.constants.MessageEnum;
import cn.juque.common.exception.AppException;
import cn.juque.common.thread.BaseOperatorThreadLocal;
import cn.juque.common.thread.TraceLogTreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>DubboExceptionFilter</li>
 * </ul>
 * @date 2024-03-13
 **/
@Slf4j
@Component
@Activate(group = {CommonConstants.PROVIDER}, order = 1)
public class DubboExceptionFilter implements Filter {

    private BaseOperatorThreadLocal baseOperatorThreadLocal;

    private TraceLogTreadLocal traceLogTreadLocal;

    /**
     * Always call invoker.invoke() in the implementation to hand over the request to the next filter node.
     *
     * @param invoker    Invoker
     * @param invocation Invocation
     */
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        log.info("try dubbo exception filter");
        Result result = invoker.invoke(invocation);
        if (!result.hasException()) {
            this.close();
            return result;
        }
        Throwable t = result.getException();
        if (t instanceof AppException) {
            AppException appException = (AppException) t;
            BaseResponseDTO<?> requestDTO = new BaseResponseDTO<>(appException, null);
            AppResponse appResponse = new AppResponse(invocation);
            appResponse.setValue(requestDTO);
            this.close();
            return new AsyncRpcResult(CompletableFuture.supplyAsync(() -> appResponse), invocation);
        }
        Class<?> clazz = invoker.getInterface();
        String serviceName = invocation.getServiceName();
        String methodName = invocation.getMethodName();
        log.error("{}#{}#{} error", serviceName, clazz.getName(), methodName, t);
        BaseResponseDTO<?> requestDTO = new BaseResponseDTO<>(MessageEnum.SYSTEM_ERROR, null);
        AppResponse appResponse = new AppResponse(invocation);
        appResponse.setValue(requestDTO);
        this.close();
        return new AsyncRpcResult(CompletableFuture.supplyAsync(() -> appResponse), invocation);
    }

    private void close() {
        this.initBaseOperatorThreadLocal();
        if (Objects.nonNull(this.baseOperatorThreadLocal)) {
            this.baseOperatorThreadLocal.remove();
        }
        this.initTraceLogTreadLocal();
        if (Objects.nonNull(this.traceLogTreadLocal)) {
            this.traceLogTreadLocal.remove();
        }
    }

    private void initBaseOperatorThreadLocal() {
        if (Objects.isNull(this.baseOperatorThreadLocal)) {
            synchronized (this) {
                if (Objects.isNull(this.baseOperatorThreadLocal)) {
                    this.baseOperatorThreadLocal = SpringUtil.getBean(BaseOperatorThreadLocal.class);
                }
            }
        }
    }

    private void initTraceLogTreadLocal() {
        if (Objects.isNull(this.traceLogTreadLocal)) {
            synchronized (this) {
                if (Objects.isNull(this.traceLogTreadLocal)) {
                    this.traceLogTreadLocal = SpringUtil.getBean(TraceLogTreadLocal.class);
                }
            }
        }
    }
}
